package com.example.flow.flow.service;

import com.example.flow.dao.FlowApproveApplyDao;
import com.example.flow.dao.FlowProcessDao;
import com.example.flow.dao.FlowProcessExtDao;
import com.example.flow.dao.model.FlowProcess;
import com.example.flow.dao.model.FlowProcessExt;
import com.example.flow.flow.EnumDefaultApproveUserType;
import com.example.flow.flow.FlowConstant;
import com.example.flow.flow.ModelParser;
import com.example.flow.flow.cache.MemoryCacheManager;
import com.example.flow.flow.model.*;
import com.example.flow.flow.model.vo.*;
import com.example.flow.utils.GsonUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 流程模板
 *
 * @author tangaq
 * 2023/6/13
 */
@Service
public class FlowProcessService {
    private static final Logger log = LoggerFactory.getLogger(FlowProcessService.class);

    private final MemoryCacheManager cacheManager = MemoryCacheManager.getInstance();
    private Map<String, String> processId2TypeCache;
    private Map<String, FlowProcess> processType2EntityCache;
    private static final String CACHE_TYPE = "flow.process.type";
    private static final String CACHE_PROCESS = "flow.process.process";
    private static final String DEFAULT_SEPARATOR = ".";

    private final FlowProcessDao processDao;
    private final FlowProcessExtDao processExtDao;
    private final FlowApproveApplyDao flowApproveApplyDao;

    public FlowProcessService(FlowProcessDao processDao,
                              FlowProcessExtDao processExtDao,
                              FlowApproveApplyDao flowApproveApplyDao) {
        this.processDao = processDao;
        this.processExtDao = processExtDao;
        this.flowApproveApplyDao = flowApproveApplyDao;
    }

    @Transactional(rollbackFor = Exception.class)
    public String save(SaveFlowProcessReq req, String uname) {
        FlowConstant.EnumFlowProcessType enumProcessType = FlowConstant.EnumFlowProcessType.find(req.getProcessType());
        if (enumProcessType == null) {
            throw new RuntimeException("流程类型有误");
        }
        this.checkParam(req);
        FlowProcess exist = processDao.findLatestByProcessType(req.getProcessType());
        FlowProcess process = new FlowProcess(req);
        if (StringUtils.isBlank(req.getProcessId())) {
            // 需求要求新增报错
            if (exist != null) {
                throw new RuntimeException("已存在[" + enumProcessType.getDesc() + "]模板");
            }
            process.setCreator(uname);
        } else {
            if (StringUtils.isBlank(process.getCreator())) {
                process.setCreator(uname);
            }
            process.setModifier(uname);
        }

        if (exist != null) {
            process.setCreator(exist.getCreator());
            process.setCts(exist.getCts());
        }
        List<TaskParse> taskParses = req.getTasks();

        FlowNodes flowNodes = new FlowNodes();
        StartModel startModel = new StartModel();
        startModel.setPostInterceptors("com.ah.ues.service.flow.approve.intercept.SaveApplyAndSendNotifyInterceptor");
        flowNodes.setStart(startModel);

        EndModel endModel = new EndModel();
        endModel.setPostInterceptors("com.ah.ues.service.flow.approve.intercept.EndCommonApprovalInterceptor");
        flowNodes.setEnd(endModel);

        Set<String> taskNames = new HashSet<>();
        TaskModel task;
        List<TaskModel> tasks = new LinkedList<>();
        for (TaskParse taskParse : taskParses) {
            String taskName = taskParse.getName();
            if (StringUtils.isBlank(taskName)) {
                throw new RuntimeException("任务节点名称为空");
            }
            task = new TaskModel(taskParse);
            if (taskNames.contains(taskName)) {
                throw new RuntimeException("任务节点名称重复:" + taskName);
            }
            Integer approverType = taskParse.getApproverType();
            EnumDefaultApproveUserType enumDefaultApproveUserType = EnumDefaultApproveUserType.find(approverType);
            if (enumDefaultApproveUserType == null) {
                throw new RuntimeException("不支持的审批人类型");
            }
            if (enumDefaultApproveUserType == EnumDefaultApproveUserType.TEMPLATE_SET) {
                if (CollectionUtils.isEmpty(taskParse.getApprover())) {
                    throw new RuntimeException(String.format("节点名:%s,模板指定审批人列表为空", taskName));
                }
            }
            taskNames.add(taskName);
            task.setPostInterceptors("com.ah.ues.service.flow.approve.intercept.DefaultCommonApprovalInterceptor");
            tasks.add(task);
        }
        flowNodes.setTasks(tasks);
        process.setFlowNodes(flowNodes);

        process.setUts(System.currentTimeMillis());
        process.setProcessId(UUID.randomUUID().toString().replace("-", ""));

        Integer version = processDao.getLatestProcessVersionByType(req.getProcessType());
        if (version == null || version < 0) {
            process.setVersion(0);
        } else {
            process.setVersion(version + 1);
        }
        process.setState(FlowConstant.STATE_ACTIVE);
        // 终止
        // process.setState(FlowConstant.STATE_FINISH);

        // 先将历史版本更改为结束
        processDao.updateUnUseStateByProcessType(req.getProcessType());
        processDao.insert(process);
        FlowProcessExt ext = new FlowProcessExt();
        ext.setProcessId(process.getProcessId());
        ext.setContent(GsonUtil.obj2Json(flowNodes));

        processExtDao.insert(ext);

        // 补充数据
        ProcessModel model = ModelParser.parse(flowNodes);
        process.setModel(model);
        process.setFlowNodes(flowNodes);
        return process.getProcessId();
    }

    private void checkParam(SaveFlowProcessReq req) {
    }

    private FlowProcess findProcessByDb(String processId) {
        FlowProcess process = processDao.findByProcessId(processId);
        if (process == null) {
            return null;
        }
        FlowProcessExt ext = processExtDao.findByProcessId(processId);
        if (ext != null) {
            process.setFlowNodes(GsonUtil.json2Obj(ext.getContent(), FlowNodes.class));
        }
        return process;
    }

    public FlowProcess findByProcessId(String processId) {
        FlowProcess process = null;
        String processType;
        Map<String, String> processId2TypeCache = ensureAvailableProcessId2TypeCache();
        Map<String, FlowProcess> processType2EntityCache = ensureAvailableProcessType2EntityCache();
        if (processId2TypeCache != null && processType2EntityCache != null) {
            processType = processId2TypeCache.get(processId);
            if (StringUtils.isNotBlank(processType)) {
                process = processType2EntityCache.get(processType);
            }
        }
        if (process != null) {
            if (log.isDebugEnabled()) {
                log.debug("process[processId={}] from cache.", processId);
            }
            return process;
        }
        process = this.findProcessByDb(processId);
        if (process != null) {
            if (log.isDebugEnabled()) {
                log.debug("process[processId={}] from db.", processId);
            }
            cache(process);
        }
        return process;
    }

    public FlowProcess findLatestByProcessType(String processType) {
        FlowProcess process = processDao.findLatestByProcessType(processType);
        if (process == null) {
            return null;
        }
        FlowProcessExt ext = processExtDao.findByProcessId(process.getProcessId());
        if (ext != null) {
            process.setFlowNodes(GsonUtil.json2Obj(ext.getContent(), FlowNodes.class));
        }
        return this.findByProcessId(process.getProcessId());
    }

    private void cache(FlowProcess process) {
        Map<String, String> processId2typeCache = ensureAvailableProcessId2TypeCache();
        Map<String, FlowProcess> processCache = ensureAvailableProcessType2EntityCache();
        if (process.getModel() == null && process.getFlowNodes() != null) {
            process.setModel(ModelParser.parse(process.getFlowNodes()));
        }
        String processType = process.getProcessType() + DEFAULT_SEPARATOR + process.getVersion();
        if (processId2typeCache != null && processCache != null) {
            if (log.isDebugEnabled()) {
                log.debug("cache processId is[{}],type is[{}]", process.getProcessId(), processType);
            }
            processCache.put(processType, process);
            processId2typeCache.put(process.getProcessId(), processType);
        } else {
            log.debug("no cache implementation class");
        }
    }

    private void clearCache() {
        Map<String, String> typeCache = ensureAvailableProcessId2TypeCache();
        typeCache.clear();
        Map<String, FlowProcess> processMap = ensureAvailableProcessType2EntityCache();
        processMap.clear();
    }

    private Map<String, String> ensureAvailableProcessId2TypeCache() {
        Map<String, String> typeCache = ensureTypeCache();
        if (typeCache == null && this.cacheManager != null) {
            typeCache = this.cacheManager.getCache(CACHE_TYPE);
            processId2TypeCache = typeCache;
        }
        return typeCache;
    }

    private Map<String, FlowProcess> ensureAvailableProcessType2EntityCache() {
        Map<String, FlowProcess> processCache = ensureProcessCache();
        if (processCache == null && this.cacheManager != null) {
            processCache = this.cacheManager.getCache(CACHE_PROCESS);
            processType2EntityCache = processCache;
        }
        return processCache;
    }

    public Map<String, FlowProcess> ensureProcessCache() {
        return processType2EntityCache;
    }

    public void setProcessType2EntityCache(Map<String, FlowProcess> processType2EntityCache) {
        this.processType2EntityCache = processType2EntityCache;
    }

    public Map<String, String> ensureTypeCache() {
        return processId2TypeCache;
    }

    public void setProcessId2TypeCache(Map<String, String> processId2TypeCache) {
        this.processId2TypeCache = processId2TypeCache;
    }
}
